package main

import (
	"./constants"
	"./entity"
	"./monitor"
	"./trace"
	"container/list"
	"flag"
	"fmt"
	"io/ioutil"
	"math"
	"math/rand"
	"net"
	"net/http"
	"os"
	"runtime"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"
)

var userParams = entity.UserParams{}
var resultList = list.New()
var netMonitor = monitor.NewNetMonitor()
var currentOs = runtime.GOOS

func init() {
	flag.StringVar(&userParams.Method, "m", "GET", "请求方法 目前支持： GET/POST")
	flag.StringVar(&userParams.UnitOfTime, "ut", constants.MS, "报表时间单位 ms(毫秒)/us(微妙)/ns(纳秒)")
	flag.BoolVar(&userParams.TraceRequest, "tr", false, "是否统计请求各阶段耗时")
	flag.BoolVar(&userParams.DisableKeepAlive, "ka", false, "是否关闭长连接")
	flag.BoolVar(&userParams.MonitorNetwork, "n", false, "是否监控各网卡每秒钟接收/发送字节数")
	flag.StringVar(&userParams.Url, "u", "", "要请求的链接 默认空")
	flag.StringVar(&userParams.Headers, "h", "", "请求头, 格式 : Content-Type:application/json,content-encoding:gzip 默认空")
	flag.StringVar(&userParams.Body, "b", "", "POST请求的请求体 默认空，如果有值，执行POST请求，没有值执行GET请求")
	flag.IntVar(&userParams.Qps, "q", 1, "每秒发送请求数，QPS")
	flag.IntVar(&userParams.Time, "t", 1, "持续多长时间，单位秒")
	flag.Int64Var(&userParams.Timeout, "to", 1000, "请求超时时间，单位ms(毫秒)")
	flag.IntVar(&userParams.Interval, "i", 50, "生成耗时报表的时间区间，单位ms(毫秒)，报表样式： 用时0~50ms的请求数...  用时50~100ms的请求数...")
	flag.IntVar(&userParams.SendingRate, "s", 3, "请求速率，说明：当qps设置的比较大，或者服务端响应请求较慢时，1秒钟可能无法达到qps参数要求的值，\n通过调整此字段，可以提高请求的发送速度，但相应的也会增加本地线程耗时，有可能出现耗时统计不精确的问题，cpu性能一般的情况下，不建议大于3")
}

func main() {
	flag.Parse()
	if len(userParams.Url) < 1 {
		flag.PrintDefaults()
	} else {
		fmt.Println("URL:" + userParams.Url)
		fmt.Println("QPS:" + strconv.Itoa(userParams.Qps))
		request()
	}
}

func isLinux() bool {
	return currentOs == constants.LINUX
}

func request() {
	client := &http.Client{}
	buildHttpClient(client)
	userParams.TotalSleepTime = 0
	// 保存请求响应结果
	var resultChan = make(chan int, userParams.Qps*userParams.Time)
	// 限制协程数

	i := float64(runtime.NumCPU()) * 1.5
	sendRate := int(i) * userParams.SendingRate
	var limitCh = make(chan int, sendRate)
	// 记录程序运行的开始时间
	startTimeNano := time.Now()
	if userParams.MonitorNetwork && isLinux() {
		monitor.FetchNetworkInfo(netMonitor)
	}
	for i := 0; i < userParams.Time; i++ {
		// 记录发起qps数个请求，耗时
		start := time.Now()
		for n := userParams.Qps; n > 0; n-- {
			limitCh <- 1
			go exec(&resultChan, client, &limitCh)
		}
		// qps数个请求，启动成功后，计算启动耗时
		usedTime := time.Since(start)

		qpsTime := usedTime / time.Millisecond
		var sleepTimeMs time.Duration
		if qpsTime > 1000 {
			fmt.Printf("第"+strconv.Itoa(i+1)+"个QPS耗时%d毫秒，进程将休息:%d 毫秒\n", qpsTime, 0)
		} else {
			sleepTimeMs = (time.Second - usedTime%time.Second) / time.Millisecond
			fmt.Printf("第"+strconv.Itoa(i+1)+"个QPS耗时%d毫秒，进程将休息:%d 毫秒\n", qpsTime, sleepTimeMs)
		}
		duration := sleepTimeMs * time.Millisecond
		time.Sleep(duration)
		userParams.TotalSleepTime += duration.Nanoseconds()
	}

	if userParams.MonitorNetwork && isLinux() {
		monitor.FetchNetworkInfo(netMonitor)
	}

	close(limitCh)
	printResult(resultChan, startTimeNano)
}
func conversionTime(nanoseconds int64, unit string) int64 {
	if unit == constants.MS {
		return nanoseconds / time.Millisecond.Nanoseconds()
	} else if unit == constants.US {
		return nanoseconds / time.Microsecond.Nanoseconds()
	} else {
		return nanoseconds
	}
}

func fetchTimeUnit() string {
	lower := strings.ToLower(userParams.UnitOfTime)
	if lower == constants.US {
		return constants.US
	} else if lower == constants.NS {
		return constants.NS
	}
	return constants.MS
}

func printResult(resultChan chan int, startTimeNano time.Time) {

	resultMap := map[int]int{}
	for {
		select {
		case _ = <-resultChan:
			// 判断所有请求是否完成
			if userParams.Qps*userParams.Time == resultList.Len() {
				// 程序执行所有的请求实际总耗时
				realTotalUsedTime := time.Since(startTimeNano)
				fmt.Println("\n所有请求发送完毕!")

				// 请求耗时最久的时间
				var maxUsedTime int64 = 0
				var minUsedTime int64 = math.MaxInt64
				// 每个请求的耗时总和
				var totalUsedTimeOfAllRequest int64 = 0

				var maxFindDnsUsedTime int64 = math.MinInt64
				var minFindDnsUsedTime int64 = math.MaxInt64
				var totalFindDnsUsedTime int64 = 0
				var avgFindDnsUsedTime int64 = 0

				var maxCreateConnectionUsedTime int64 = math.MinInt64
				var minCreateConnectionUsedTime int64 = math.MaxInt64
				var totalCreateConnectionUsedTime int64 = 0
				var avgCreateConnectionUsedTime int64 = 0

				var maxSendHeaderUsedTime int64 = math.MinInt64
				var minSendHeaderUsedTime int64 = math.MaxInt64
				var totalSendHeaderUsedTime int64 = 0
				var avgSendHeaderUsedTime int64 = 0

				var maxSendBodyUsedTime int64 = math.MinInt64
				var minSendBodyUsedTime int64 = math.MaxInt64
				var totalSendBodyUsedTime int64 = 0
				var avgSendBodyUsedTime int64 = 0

				var maxWaitResponseUsedTime int64 = math.MinInt64
				var minWaitResponseUsedTime int64 = math.MaxInt64
				var totalWaitResponseUsedTime int64 = 0
				var avgWaitResponseUsedTime int64 = 0

				var maxReadResponseUsedTime int64 = math.MinInt64
				var minReadResponseUsedTime int64 = math.MaxInt64
				var totalReadResponseUsedTime int64 = 0
				var avgReadResponseUsedTime int64 = 0
				n := 0
				randInt := rand.Intn(resultList.Len())
				for v := resultList.Front(); v != nil; v = v.Next() {
					result := v.Value.(*entity.Result)
					if n == randInt {
						fmt.Println(result.ResponseBody)
					}
					n++
					// 所有请求的耗时总和
					totalUsedTimeOfAllRequest = totalUsedTimeOfAllRequest + result.TotalTime
					// 找最大耗时
					if result.TotalTime > maxUsedTime {
						maxUsedTime = result.TotalTime
					}
					// 找最小耗时
					if result.TotalTime < minUsedTime {
						minUsedTime = result.TotalTime
					}
					result.TotalTime = result.TotalTime / time.Millisecond.Nanoseconds()
					statisticStatusCode(result, resultMap)
					// 计算最大查找dns时间
					if result.FindDnsUsedTime > maxFindDnsUsedTime {
						maxFindDnsUsedTime = result.FindDnsUsedTime
					}
					// 计算最小查找dns时间
					if result.FindDnsUsedTime < minFindDnsUsedTime {
						minFindDnsUsedTime = result.FindDnsUsedTime
					}
					// 计算所有查找dns时间的总和
					totalFindDnsUsedTime += result.FindDnsUsedTime
					// 计算最大创建连接时间
					if result.CreateConnUsedTime > maxCreateConnectionUsedTime {
						maxCreateConnectionUsedTime = result.CreateConnUsedTime
					}
					// 计算最小创建连接时间
					if result.CreateConnUsedTime < minCreateConnectionUsedTime {
						minCreateConnectionUsedTime = result.CreateConnUsedTime
					}
					// 计算所有创建连接时间的总和
					totalCreateConnectionUsedTime += result.CreateConnUsedTime
					// 计算最大发送请求头时间
					if result.SendHeaderUsedTime > maxSendHeaderUsedTime {
						maxSendHeaderUsedTime = result.SendHeaderUsedTime
					}
					// 计算最小发送请求头时间
					if result.SendHeaderUsedTime < minSendHeaderUsedTime {
						minSendHeaderUsedTime = result.SendHeaderUsedTime
					}
					// 计算所有发送请求头时间的总和
					totalSendHeaderUsedTime += result.SendHeaderUsedTime
					// 计算最大发送请求体时间
					if result.SendRequestUsedTime > maxSendBodyUsedTime {
						maxSendBodyUsedTime = result.SendRequestUsedTime
					}
					// 计算最小发送请求体时间
					if result.SendRequestUsedTime < minSendBodyUsedTime {
						minSendBodyUsedTime = result.SendRequestUsedTime
					}
					// 计算所有发送请求体时间的总和
					totalSendBodyUsedTime += result.SendRequestUsedTime
					// 计算最大等待响应时间
					if result.WaitResponseUsedTime > maxWaitResponseUsedTime {
						maxWaitResponseUsedTime = result.WaitResponseUsedTime
					}
					// 计算最小等待响应时间
					if result.WaitResponseUsedTime < minWaitResponseUsedTime {
						minWaitResponseUsedTime = result.WaitResponseUsedTime
					}
					// 计算所有等待响应时间的总和
					totalWaitResponseUsedTime += result.WaitResponseUsedTime
					// 计算最大响应传输时间
					if result.FetchResponseUsedTime > maxReadResponseUsedTime {
						maxReadResponseUsedTime = result.FetchResponseUsedTime
					}
					// 计算最小响应传输时间
					if result.FetchResponseUsedTime < minReadResponseUsedTime {
						minReadResponseUsedTime = result.FetchResponseUsedTime
					}
					// 计算所有响应传输时间的总和
					totalReadResponseUsedTime += result.FetchResponseUsedTime
				}
				//fmt.Println(resultMap)
				for k, v := range resultMap {
					if k == 0 {
						fmt.Printf("超时的请求数:%d\n", v)
					} else {
						fmt.Printf("状态码为:%d 的请求数:%d\n", k, v)
					}
				}
				// 总请求数==总entity.Result数
				totalRequestNumber := resultList.Len()
				totalRequestNumberInt64 := int64(totalRequestNumber)
				// 查找Dns平均耗时 = 所有查找Dns耗时总和 / 总请求数
				avgFindDnsUsedTime = totalFindDnsUsedTime / totalRequestNumberInt64
				// 创建连接平均耗时 = 所有请求创建连接总和 / 总请求数
				avgCreateConnectionUsedTime = totalCreateConnectionUsedTime / totalRequestNumberInt64
				// 发送请求头平均耗时 = 所有请求发送请求头耗时总和 / 总请求数
				avgSendHeaderUsedTime = totalSendHeaderUsedTime / totalRequestNumberInt64
				// 发送请求体平均耗时 = 所有请求发送请求体耗时总和 / 总请求数
				avgSendBodyUsedTime = totalSendBodyUsedTime / totalRequestNumberInt64
				// 等到响应平均耗时 = 所有请请求等到响应总耗时 / 总请求数
				avgWaitResponseUsedTime = totalWaitResponseUsedTime / totalRequestNumberInt64
				// 传输响应平均耗时 = 所有响应传输耗时总和 / 总请求数
				avgReadResponseUsedTime = totalReadResponseUsedTime / totalRequestNumberInt64
				// 所有请求执行成功后，程序的运行时间，转换成毫秒值
				realTotalUsedTimeMs := realTotalUsedTime.Nanoseconds() / time.Millisecond.Nanoseconds()
				// 请求的平均耗时==所有请求的耗时之和/总请求数
				avgUsedTimeMs := totalUsedTimeOfAllRequest / totalRequestNumberInt64 / time.Millisecond.Nanoseconds()
				maxUsedTimeMs := maxUsedTime / time.Millisecond.Nanoseconds()
				minUsedTimeMs := minUsedTime / time.Millisecond.Nanoseconds()
				realUsedSecond := realTotalUsedTime.Nanoseconds() / time.Second.Nanoseconds()
				var adviceQps int64
				if realUsedSecond == 0 {
					adviceQps = totalRequestNumberInt64
				} else {
					if userParams.TotalSleepTime > 0 {
						adviceQps = totalRequestNumberInt64 / (int64(userParams.Time) - (userParams.TotalSleepTime / time.Second.Nanoseconds()))
					} else {
						adviceQps = totalRequestNumberInt64 / realUsedSecond
					}
				}
				// 根据命令指定的用时区间，输出属于各区间的请求数（用时50~100ms	的请求数：	1 	占比：	100.00%）
				buildPerIntervalUsedTime(maxUsedTimeMs, resultList)

				if userParams.TraceRequest {
					fmt.Printf("\n")
				}

				if userParams.TraceRequest {
					fmt.Printf("\n"+
						"查找DNS最大用时:\t%d "+fetchTimeUnit()+"\n"+
						"查找DNS最小用时:\t%d "+fetchTimeUnit()+"\n"+
						"查找DNS平均用时:\t%d "+fetchTimeUnit()+"\n"+
						"创建连接最大用时:\t%d "+fetchTimeUnit()+"\n"+
						"创建连接最小用时:\t%d "+fetchTimeUnit()+"\n"+
						"创建连接平均用时:\t%d "+fetchTimeUnit()+"\n"+
						"发送请求头最大用时:\t%d "+fetchTimeUnit()+"\n"+
						"发送请求头最小用时:\t%d "+fetchTimeUnit()+"\n"+
						"发送请求头平均用时:\t%d "+fetchTimeUnit()+"\n"+
						"发送请求体最大用时:\t%d "+fetchTimeUnit()+"\n"+
						"发送请求体最小用时:\t%d "+fetchTimeUnit()+"\n"+
						"发送请求体平均用时:\t%d "+fetchTimeUnit()+"\n"+
						"等待响应最大用时:\t%d "+fetchTimeUnit()+"\n"+
						"等待响应最小用时:\t%d "+fetchTimeUnit()+"\n"+
						"等待响应平均用时:\t%d "+fetchTimeUnit()+"\n"+
						"传输响应最大用时:\t%d "+fetchTimeUnit()+"\n"+
						"传输响应最小用时:\t%d "+fetchTimeUnit()+"\n"+
						"传输响应平均用时:\t%d "+fetchTimeUnit()+"\n",
						conversionTime(maxFindDnsUsedTime, userParams.UnitOfTime),
						conversionTime(minFindDnsUsedTime, userParams.UnitOfTime),
						conversionTime(avgFindDnsUsedTime, userParams.UnitOfTime),
						conversionTime(maxCreateConnectionUsedTime, userParams.UnitOfTime),
						conversionTime(minCreateConnectionUsedTime, userParams.UnitOfTime),
						conversionTime(avgCreateConnectionUsedTime, userParams.UnitOfTime),
						conversionTime(maxSendHeaderUsedTime, userParams.UnitOfTime),
						conversionTime(minSendHeaderUsedTime, userParams.UnitOfTime),
						conversionTime(avgSendHeaderUsedTime, userParams.UnitOfTime),
						conversionTime(maxSendBodyUsedTime, userParams.UnitOfTime),
						conversionTime(minSendBodyUsedTime, userParams.UnitOfTime),
						conversionTime(avgSendBodyUsedTime, userParams.UnitOfTime),
						conversionTime(maxWaitResponseUsedTime, userParams.UnitOfTime),
						conversionTime(minWaitResponseUsedTime, userParams.UnitOfTime),
						conversionTime(avgWaitResponseUsedTime, userParams.UnitOfTime),
						conversionTime(maxReadResponseUsedTime, userParams.UnitOfTime),
						conversionTime(minReadResponseUsedTime, userParams.UnitOfTime),
						conversionTime(avgReadResponseUsedTime, userParams.UnitOfTime))
				}

				if userParams.TraceRequest {
					fmt.Printf("\n")
				}

				fmt.Printf("\n总请求数:\t%d\n"+
					"实际耗时:\t%d ms\n"+
					"最大耗时:\t%dms\n"+
					"最小耗时:\t%dms\n"+
					"平均用时:\t%d ms\n"+
					"建议QPS:\t%d\n",

					totalRequestNumber,
					realTotalUsedTimeMs,
					maxUsedTimeMs,
					minUsedTimeMs,
					avgUsedTimeMs,

					adviceQps)

				if userParams.MonitorNetwork && isLinux() {

					fmt.Println()
					networkUsage := monitor.CalcNetworkSendUsage(netMonitor, realUsedSecond)
					fmt.Println()
					for k, v := range networkUsage {
						if v > 0 {
							fmt.Printf("网卡\t%s\t平均每秒发送\t%.4f\tMB数据\n", k, v)
						}
					}

					fmt.Println()
					networkUsage = monitor.CalcNetworkReceiveUsage(netMonitor, realUsedSecond)
					for k, v := range networkUsage {
						if v > 0 {
							fmt.Printf("网卡\t%s\t平均每秒接收\t%.4f\tMB数据\n", k, v)
						}
					}
				}
				os.Exit(0)
			}
		}
	}
}

/**
统计状态码
*/
func statisticStatusCode(result *entity.Result, resultMap map[int]int) {
	statusCode := result.ResponseStatus
	if _, i := resultMap[statusCode]; i {
		resultMap[statusCode] = resultMap[statusCode] + 1
	} else {
		resultMap[statusCode] = 1
	}
}

/**
将请求耗时以userParams.Interval的值作为一个区间，统计每个区间的请求数
*/
func buildPerIntervalUsedTime(maxUsedTimeMs int64, resultArr *list.List) {
	interval := int(maxUsedTimeMs/int64(userParams.Interval) + 1)
	var intervalMap = map[int]int{}
	n := 0
	for v := resultArr.Front(); v != nil; v = v.Next() {
		result := v.Value.(*entity.Result)
		for i := 1; i <= interval; i++ {
			n++
			if n%1000 == 0 {
				time.Sleep(1)
				n = 0
			}
			if int64(i*userParams.Interval) > result.TotalTime {
				if _, b := intervalMap[i*userParams.Interval]; b {
					intervalMap[i*userParams.Interval] = intervalMap[i*userParams.Interval] + 1
				} else {
					intervalMap[i*userParams.Interval] = 1
				}
				break
			}
		}
	}
	var keys []int
	for k := range intervalMap {
		keys = append(keys, k)
	}
	sort.Ints(keys)
	fmt.Println()
	for i := len(keys) - 1; i >= 0; i-- {
		k := keys[i]
		fmt.Println("用时" + strconv.Itoa(k-userParams.Interval) + "~" + strconv.Itoa(k) + "ms\t的请求数：\t" + strconv.Itoa(intervalMap[k]) + " \t占比：\t" + strconv.FormatFloat(float64(intervalMap[k])/float64(resultArr.Len())*100, 'f', 2, 64) + "%")
	}
}

func buildHttpClient(client *http.Client) {

	tr := &http.Transport{
		DialContext:       (&net.Dialer{}).DialContext,
		DisableKeepAlives: userParams.DisableKeepAlive,
	}

	if userParams.Timeout != 0 {
		client = &http.Client{Transport: tr, Timeout: time.Duration(userParams.Timeout) * time.Nanosecond}
	} else {
		client = &http.Client{Transport: tr}
	}

}

func exec(resultChan *chan int, client *http.Client, limitCh *chan int) {
	var req *http.Request
	var err error
	// 初始化entity.Result携程处理完成将存储到ResultList中
	var result = entity.NewResult()
	// 如果request body是空的,就不在req中设置body。
	if len(userParams.Body) <= 0 {
		req, err = http.NewRequest(userParams.Method, userParams.Url, nil)
	} else {
		req, err = http.NewRequest(userParams.Method, userParams.Url, strings.NewReader(userParams.Body))
	}
	// 判断是否要跟踪请求的各阶段耗时
	if userParams.TraceRequest {
		req = trace.BuildTraceRequest(req, &result.UsedTime)
	}
	if err != nil {
		// 设置错误类型为创建请求失败
		result.ErrorType = constants.NewRequestError
		*resultChan <- appendResult(result)
		return
	}
	// 在http.client.Do方法之前，标记请求的开始时间，用来计算请求用时
	cStart := time.Now()
	resp, errD := client.Do(req)
	if errD != nil {
		// 标记url请求失败。
		result.ErrorType = constants.DoRequestError
		*resultChan <- appendResult(result)
		return
	}

	defer resp.Body.Close()
	<-*limitCh
	// 读取body
	bytes, err := ioutil.ReadAll(resp.Body)
	// body读取成功，作为请求结束时间。记录本次请求的耗时
	result.TotalTime = time.Since(cStart).Nanoseconds()

	if err != nil {
		result.ErrorType = constants.ReadResponseError
		*resultChan <- appendResult(result)
		return
	}
	// 记录body
	result.ResponseBody = string(bytes)
	// 记录状态码
	result.ResponseStatus = resp.StatusCode
	// 获取response的headers。
	for k, v := range resp.Header {
		result.ResponseHeaders[k] = strings.Join(v, ",")
	}
	*resultChan <- appendResult(result)
}

var rwl = new(sync.RWMutex)

func appendResult(result *entity.Result) int {
	rwl.Lock()
	resultList.PushBack(result)
	rwl.Unlock()
	return 0
}
